package com.zzyl.nursing.service.impl;

import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.zzyl.common.constant.CacheConstants;
import com.zzyl.common.utils.DateUtils;
import com.zzyl.nursing.domain.AlertData;
import com.zzyl.nursing.domain.DeviceData;
import com.zzyl.nursing.mapper.DeviceMapper;
import com.zzyl.nursing.service.IAlertDataService;
import com.zzyl.system.mapper.SysUserRoleMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import com.zzyl.nursing.mapper.AlertRuleMapper;
import com.zzyl.nursing.domain.AlertRule;
import com.zzyl.nursing.service.IAlertRuleService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 报警规则Service业务层处理
 *
 * @author ruoyi
 * @date 2025-01-09
 */
@Service
@Slf4j
public class AlertRuleServiceImpl extends ServiceImpl<AlertRuleMapper, AlertRule> implements IAlertRuleService {
    @Autowired
    private AlertRuleMapper alertRuleMapper;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Autowired
    private DeviceMapper deviceMapper;

    @Autowired
    private SysUserRoleMapper sysUserRoleMapper;

    @Autowired
    private IAlertDataService alertDataService;

    @Value("${alert.deviceMaintainerRole}")
    private String deviceMaintainerRole;

    @Value("${alert.managerRole}")
    private String managerRole;


    /**
     * 查询报警规则
     *
     * @param id 报警规则主键
     * @return 报警规则
     */
    @Override
    public AlertRule selectAlertRuleById(Long id) {
        return getById(id);
    }

    /**
     * 查询报警规则列表
     *
     * @param alertRule 报警规则
     * @return 报警规则
     */
    @Override
    public List<AlertRule> selectAlertRuleList(AlertRule alertRule) {
        return alertRuleMapper.selectAlertRuleList(alertRule);
    }

    /**
     * 新增报警规则
     *
     * @param alertRule 报警规则
     * @return 结果
     */
    @Override
    public int insertAlertRule(AlertRule alertRule) {
        return save(alertRule) ? 1 : 0;
    }

    /**
     * 修改报警规则
     *
     * @param alertRule 报警规则
     * @return 结果
     */
    @Override
    public int updateAlertRule(AlertRule alertRule) {
        return updateById(alertRule) ? 1 : 0;
    }

    /**
     * 批量删除报警规则
     *
     * @param ids 需要删除的报警规则主键
     * @return 结果
     */
    @Override
    public int deleteAlertRuleByIds(Long[] ids) {
        return removeByIds(Arrays.asList(ids)) ? 1 : 0;
    }

    /**
     * 删除报警规则信息
     *
     * @param id 报警规则主键
     * @return 结果
     */
    @Override
    public int deleteAlertRuleById(Long id) {
        return removeById(id) ? 1 : 0;
    }

    @Override
    public void alertFilter() {
        //1.查询有没有效的规则，条件查询报警规则表，条件为status为1
        Long count = lambdaQuery().eq(AlertRule::getStatus, 1).count();
        //做判断，没有规则就结束
        if (count == 0) {
            log.info("没有有效的报警规则");
            return;
        }
        //2.查询所有设备上报的数据
        List<Object> allDataList = redisTemplate.opsForHash().values(CacheConstants.IOT_DEVICEDATA_LATEST);
        if (CollUtil.isEmpty(allDataList)) {
            log.info("没有设备上报的数据");
            return;
        }
        //3.解析设备上报的数据到一个新的List中
        List<DeviceData> deviceDataList = new ArrayList<>();
        allDataList.forEach(data -> {
            //上报一次的数据
            List<DeviceData> dataList = JSONUtil.toList((String) data, DeviceData.class);
            deviceDataList.addAll(dataList);
        });
        deviceDataList.forEach(this::alertFilter);
    }
/**
 * 报警数据过滤处理
 */


    private void alertFilter(DeviceData deviceData) {
        //准备对应的报警规则数据
        List<AlertRule> allRulesList = new ArrayList<>();
        //针对一个产品下所有的设备规则
        List<AlertRule> productRuleList = getRulesByIotId(deviceData.getProductKey(), deviceData.getFunctionId());
        //只针对一个设备的规则
        List<AlertRule> deviceRulesList = getRulesByIotId(deviceData.getIotId(), deviceData.getFunctionId());
        //进行合并
        if (CollUtil.isNotEmpty(productRuleList)) {
            allRulesList.addAll(productRuleList);
        }
        if (CollUtil.isNotEmpty(deviceRulesList)) {
            allRulesList.addAll(deviceRulesList);
        }
        //判断规则是否为空
        if (CollUtil.isNotEmpty(allRulesList)) {
            allRulesList.forEach(alertRule -> deviceDataAlarmHandler(alertRule, deviceData));

        }
    }

    /**
     * 对每个规则进行过滤处理
     * @param alertRule
     * @param deviceData
     */

    private void deviceDataAlarmHandler(AlertRule alertRule, DeviceData deviceData) {
        //第一层校验
        //判断上报的数据是否存在规则的生效时间内
        //上报数据的时间
        LocalTime alarmTime = deviceData.getAlarmTime().toLocalTime();
        //规则的生效时间段内 00:00:00~23:59:59, 段就有开始，有结束
        String alertEffectivePeriod = alertRule.getAlertEffectivePeriod();
        String[] split = alertEffectivePeriod.split("~");
        LocalTime start = LocalTime.parse(split[0]);
        LocalTime end = LocalTime.parse(split[1]);
        //比较
        if (alarmTime.isBefore(start) || alarmTime.isAfter(end)) {
            return;
        }

        //第二层校验：判断数据阈值,谁跟谁比较，上报的数据与规则设置的阈值
        //规则里设置的阈值
        Double value = alertRule.getValue();
        //上报的数据
        String dataValueString = deviceData.getDataValue();
        Double dataValue = Double.valueOf(dataValueString);
        //检验数据是否达到阈值, p1>p2则返回1，p1<p2返回-1，p1=p2返回0
        int compareResult = NumberUtil.compare(dataValue, value);
        //操作符比较
        String operator = alertRule.getOperator();
        String iotId = deviceData.getIotId();
        //统计次数的key
        String aggCountKey = CacheConstants.ALERT_TRIGGER_COUNT_PREFIX + iotId + ":" + deviceData.getFunctionId() + ":" + alertRule.getId();
        //规则的要求，上报的数据要>=阈值但实际上报的数据值小于阈值  规则的要求: 上报的数据<阈值,实际上报的数据值大于阈值
        //1.上报的数据 55， 阈值是65， operation <
        // compareResult=-1  compareResult<0报警 >=0不用报警
        //2. 上报的数据 55， 阈值是65， operation >=
        if (("<".equals(operator) && compareResult >= 0) || (">=".equals(operator) && compareResult < 0)) {
            //不用报警的情况
            //累计的次数清零
            redisTemplate.delete(aggCountKey);
            return;
        }

        //第三层校验： 判断沉默周期
        // 是否在沉默周期内, 如果redis中不存在key说明 沉默周期已结束
        String silentKey = CacheConstants.ALERT_SILENT_PREFIX + iotId + ":" + deviceData.getFunctionId() + ":" + alertRule.getId();
        if (redisTemplate.hasKey(silentKey)) {
            return;
        }


        //第四层校验：判断持续周期，累计次数>=持续周期 就要报警，
        // 不在沉默周期内，且数据满足报警阈值，计数器+1
        // 先+1，返回最新的值, +1 + 1 + 1
        Long increment = redisTemplate.opsForValue().increment(aggCountKey);
        //持续周期
        Integer duration = alertRule.getDuration();
        if (NumberUtil.compare(increment, duration) < 0) {
            //没达到持续周期
            return;
        }
        // 要报警了
        // 删除redis报警数，下次要重新计数
        redisTemplate.delete(aggCountKey);
        // 设置沉默周期，并指定key的有效期为沉默周期的时间
        redisTemplate.opsForValue().set(silentKey, "1", alertRule.getAlertSilentPeriod(), TimeUnit.MINUTES);
        //添加报警数据，查询通知的人员
        Set<Long> userIds = new HashSet<Long>();
        //是否为老人数据 报警的数据类型
        if (ObjUtil.equal(alertRule.getAlertDataType(), 0)) {
            //0是老人数据
            //判断是否为固定设备 location_type
            if (ObjectUtil.equal(deviceData.getLocationType(),0)) {
                //随身设备
                userIds.addAll(deviceMapper.selectNursingIdsByIotIdWithElder(iotId));
            } else {
                //床位
                userIds.addAll(deviceMapper.selectNursingIdsByIotIdWithBed(iotId));
            }
        } else {
            //设备数据，zhaoweixiugong
            userIds.addAll(sysUserRoleMapper.selectUserIdByRoleName(managerRole));
        }
        // 无论哪种报警都要通知超级管理员
        userIds.addAll(sysUserRoleMapper.selectUserIdByRoleName(managerRole));
        //批量添加报警数据
        insertAlertData(userIds, alertRule, deviceData);

    }

    /**
     * 保存报警数据
     *
     * @param allUserIds
     * @param rule
     * @param deviceData
     */
    private void insertAlertData(Collection<Long> allUserIds, AlertRule rule, DeviceData deviceData) {
        // 对象拷贝
        AlertData alertData = BeanUtil.toBean(deviceData, AlertData.class);
        alertData.setAlertRuleId(rule.getId());
        // 心率<60,持续3个周期就报警
        String alertReason = StrUtil.format("{}{}{},持续{}个周期就报警", rule.getFunctionName(), rule.getOperator(), rule.getValue(), rule.getDuration());
        alertData.setAlertReason(alertReason);
        alertData.setStatus(0);//没处理
        alertData.setType(rule.getAlertDataType());
        // 遍历allUserIds
        List<AlertData> list = allUserIds.stream().map(userId -> {
            // 以alertData为模板创建新的对象
            AlertData dbAlertData = BeanUtil.toBean(alertData, AlertData.class);
            dbAlertData.setUserId(userId);
            dbAlertData.setId(null);
            return dbAlertData;
        }).collect(Collectors.toList());

        // 批量保存
        alertDataService.saveBatch(list);

    }

    /**
     * 获取一个设备下的所有规则
     * @param iotId
     * @param functionId
     * @return
     */
    private List<AlertRule> getRulesByIotId(String iotId, String functionId) {
        return lambdaQuery().eq(AlertRule::getIotId,iotId)
                .eq(AlertRule::getFunctionId,functionId)
                .eq(AlertRule::getStatus,1)
                .list();
    }
    /**
     * 查询一个产品下的所有设备规则
     * @param productKey
     * @param functionId
     * @return
     */
    private List<AlertRule> getRulesByProductKey(String productKey, String functionId) {

       return lambdaQuery().eq(AlertRule::getProductKey,productKey)
               .eq(AlertRule::getFunctionId,functionId)
               .eq(AlertRule::getIotId,1)
               .eq(AlertRule::getStatus,1)
               .list();

    }
}
